﻿// ﻿// Copyright (c) Ullrich Praetz - https://github.com/friflo. All rights reserved.
// See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Friflo.Json.Fliox.Mapper;
using Friflo.Json.Fliox.Mapper.Utils;

namespace Friflo.Json.Fliox.Hub.Client.Internal.Map
{
    internal static class ClientEntityUtils
    {
        private static readonly Dictionary<Type, EntitySetInfo[]>   EntitySetInfoCache      = new Dictionary<Type, EntitySetInfo[]>();
        private static readonly Dictionary<Type, IEntitySetMapper>  EntitySetMapperCache    = new Dictionary<Type, IEntitySetMapper>();
        
        private static readonly StoreConfig DefaultStoreConfig = new StoreConfig();
        
        private static IEntitySetMapper GetEntitySetMapper(Type type) {
            var cache = EntitySetMapperCache;
            if (cache.TryGetValue(type, out var mapper)) {
                return mapper;
            }
            mapper = (IEntitySetMapper)EntitySetMatcher.CreateMapper(type, DefaultStoreConfig);
            cache[type] = mapper;
            return mapper;
        }
        
        internal static EntitySetInfo[] GetEntitySetInfos(Type client) {
            var cache = EntitySetInfoCache;
            lock (cache) {
                if (cache.TryGetValue(client, out  EntitySetInfo[] result)) {
                    return result;
                }
                var index = 0;
                var entityInfos = new List<EntitySetInfo>();
                var flags       = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
                PropertyInfo[] properties = client.GetProperties(flags);
                for (int n = 0; n < properties.Length; n++) {
                    var  property       = properties[n];
                    Type propType       = property.PropertyType;
                    bool isEntitySet    = IsEntitySet(propType);
                    if (!isEntitySet)
                        continue;
                    var mapper      = GetEntitySetMapper(propType);
                    var genericArgs = propType.GetGenericArguments();
                    var info        = new EntitySetInfo (index++, propType, genericArgs[0], genericArgs[1], client, mapper, property );
                    entityInfos.Add(info);
                }
                FieldInfo[] fields = client.GetFields(flags);
                for (int n = 0; n < fields.Length; n++) {
                    var  field          = fields[n];
                    Type fieldType      = field.FieldType;
                    bool isEntitySet    = IsEntitySet(fieldType);
                    if (!isEntitySet || IsAutoGeneratedBackingField(field))
                        continue;
                    var mapper      = GetEntitySetMapper(fieldType);
                    var genericArgs = fieldType.GetGenericArguments();
                    var info        = new EntitySetInfo (index++, fieldType, genericArgs[0], genericArgs[1], client, mapper, field);
                    entityInfos.Add(info);
                }
                result = entityInfos.ToArray();
                cache.Add(client, result);
                return result;
            }
        }
        
        internal static Type[] GetEntityTypes(Type clientType) {
            var entityInfos = GetEntitySetInfos (clientType);
            var types       = new Type[entityInfos.Length];
            for (int n = 0; n < entityInfos.Length; n++) {
                types[n] = entityInfos[n].entityType;
            }
            return  types;
        }

        internal static bool IsEntitySet (Type type) {
            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(EntitySet<,>);
        }
        
        private static bool IsAutoGeneratedBackingField(FieldInfo field) {
            foreach (CustomAttributeData attr in field.CustomAttributes) {
                if (attr.AttributeType == typeof(CompilerGeneratedAttribute))
                    return true;
            }
            return false;
        }
    }
    
    public readonly struct EntitySetInfo
    {
        public   readonly   string              memberName;
        public   readonly   string              container;
        public   readonly   ShortString         containerShort;
        public   readonly   Type                keyType;
        public   readonly   Type                entityType;
        //
        /// <summary>index in <see cref="FlioxClient.GetEntitySetInfos"/></summary>
        internal readonly   int                 index;
        internal readonly   Type                entitySetType;
        internal readonly   IContainerMember    containerMember;

        public   override   string              ToString() => container;

        internal EntitySetInfo (
            int                 index,
            Type                entitySetType,
            Type                keyType,
            Type                entityType,
            Type                client,
            IEntitySetMapper    mapper,
            MemberInfo          member)
        {
            this.index          = index;
            memberName          = member.Name;
            container           = AttributeUtils.GetMemberJsonName(member);
            containerShort      = new ShortString(container);
            containerMember     = mapper.CreateContainerMember(client, memberName);
            this.entitySetType  = entitySetType;
            this.keyType        = keyType;
            this.entityType     = entityType;
        }
    }
}